package com.hoolai.qps.codes.mina;

import org.apache.log4j.Logger;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.AttributeKey;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.ProtocolEncoder;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;

import com.hoolai.qps.proto.ReqCmdProto.SaveUserReqCmdProto;

//TODO $ 这个现在有问题
public class MyProtoBufCodecFactory implements ProtocolCodecFactory {

    private static final Logger logger = Logger.getLogger(MyProtoBufCodecFactory.class);

    @Override
    public ProtocolDecoder getDecoder(IoSession session) throws Exception {
        return new ProtocolDecoder() {
            
            private final AttributeKey BUFFER = new AttributeKey(getClass(), "buffer");

            @Override
            public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception {
                //do nothing
            }

            @Override
            public void dispose(IoSession session) throws Exception {
                //do nothing
            }

            @Override
            public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
                
//                if(1==1){
//                    byte[] bytes = new byte[in.limit() - 4];
//                    logger.info("in length="+in.getInt());
//                    in.get(bytes);
//                    Ping ping = Ping.parseFrom(bytes);
//                    out.write(ping);
//                }
                
                
                if (!session.getTransportMetadata().hasFragmentation()) {
                    while (in.hasRemaining()) {
                        if (!doDecode(session, in, out)) {
                            break;
                        }
                    }

                    return;
                }

                boolean usingSessionBuffer = true;
                IoBuffer buf = (IoBuffer) session.getAttribute(BUFFER);
                // If we have a session buffer, append data to that; otherwise
                // use the buffer read from the network directly.
                if (buf != null) {
                    boolean appended = false;
                    // Make sure that the buffer is auto-expanded.
                    if (buf.isAutoExpand()) {
                        try {
                            buf.put(in);
                            appended = true;
                        } catch (IllegalStateException e) {
                            // A user called derivation method (e.g. slice()),
                            // which disables auto-expansion of the parent buffer.
                        } catch (IndexOutOfBoundsException e) {
                            // A user disabled auto-expansion.
                        }
                    }

                    if (appended) {
                        buf.flip();
                    } else {
                        // Reallocate the buffer if append operation failed due to
                        // derivation or disabled auto-expansion.
                        buf.flip();
                        IoBuffer newBuf = IoBuffer.allocate(
                                buf.remaining() + in.remaining()).setAutoExpand(true);
                        newBuf.order(buf.order());
                        newBuf.put(buf);
                        newBuf.put(in);
                        newBuf.flip();
                        buf = newBuf;

                        // Update the session attribute.
                        session.setAttribute(BUFFER, buf);
                    }
                } else {
                    buf = in;
                    usingSessionBuffer = false;
                }

                for (;;) {
                    int oldPos = buf.position();
                    boolean decoded = doDecode(session, buf, out);
                    if (decoded) {
                        if (buf.position() == oldPos) {
                            throw new IllegalStateException(
                                    "doDecode() can't return true when buffer is not consumed.");
                        }

                        if (!buf.hasRemaining()) {
                            break;
                        }
                    } else {
                        break;
                    }
                }

                // if there is any data left that cannot be decoded, we store
                // it in a buffer in the session and next time this decoder is
                // invoked the session buffer gets appended to
                if (buf.hasRemaining()) {
                    if (usingSessionBuffer && buf.isAutoExpand()) {
                        buf.compact();
                    } else {
                        storeRemainingInSession(buf, session);
                    }
                } else {
                    if (usingSessionBuffer) {
                        removeSessionBuffer(session);
                    }
                }
            }

            private boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
                if (in.prefixedDataAvailable(4, 500000)) {
                    byte[] bytes = new byte[in.limit() - 4];
                    logger.info("in length="+in.getInt());
                    in.get(bytes);
                    SaveUserReqCmdProto ping = SaveUserReqCmdProto.parseFrom(bytes);
                    out.write(ping);
                    return true;
                }
                in.flip();
                return false;
            }
            
            
            private void storeRemainingInSession(IoBuffer buf, IoSession session) {
                final IoBuffer remainingBuf = IoBuffer.allocate(buf.capacity()).setAutoExpand(true);

                remainingBuf.order(buf.order());
                remainingBuf.put(buf);

                session.setAttribute(BUFFER, remainingBuf);
            }
            
            private void removeSessionBuffer(IoSession session) {
                session.removeAttribute(BUFFER);
            }
        };
        
        
    }

    @Override
    public ProtocolEncoder getEncoder(IoSession session) throws Exception {
        return new ProtocolEncoder() {

            @Override
            public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception {
                SaveUserReqCmdProto proto = (SaveUserReqCmdProto) message;
                byte[] bytes = proto.toByteArray();
                IoBuffer buf = IoBuffer.allocate(bytes.length + 4);
                buf.putInt(bytes.length);
                buf.put(bytes);
                buf.flip();
                out.write(buf);
            }

            @Override
            public void dispose(IoSession session) throws Exception {
                //do nothing
            }
        };
    }

}
